﻿#region Header File
//
// TestArq.cs - Test for Stop And Wait ARQ Protocol
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.IO;
using System.Text;
using System.IO.Ports;
using System.Threading;
using System.Diagnostics;
using Arq;
#endregion

namespace TestArq
{
    //[TestFixture]
    public class TestArq
    {
        
        struct TaskArgs
        {
            public int         id;
            public int         pktcount;
            public StopAndWait protocol;
            public MeanVar     ber;
            public MeanVar     fer;
        }

        // ingredients
        Random             rnd0;
        SerialPort         com1;
        StopAndWait        arq1;
        StopAndWait        arq2;
        FiniteQueueStream  arc1;
        FiniteQueueStream  arc2;
        FiniteQueueStream  arc3;
        FiniteQueueStream  arc4;
        ByteChannel        bch1;
        ByteChannel        bch2;
        double             bce1;
        double             bce2;        
        MeanVar            ber1;
        MeanVar            fer1;
        MeanVar            ber2;
        MeanVar            fer2;

        int                pktsSent = 0;
        int                pktsRecv = 0;       
        byte[]             testpkt;
        object             sync = new object();

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>       
        public TestArq()
        {
            testpkt = new byte[StopAndWait.MTU];
            rnd0    = new Random();            
            rnd0.NextBytes(testpkt);
        }
        #endregion

        /// <summary>
        /// Builds a 1-protocol object graph in loopback mode
        /// </summary>
        /// <param name="bc1err"></param>
        void BuildGraph1P(double bc1err)
        {
            this.bce1 = bc1err;

            ber1 = new MeanVar();
            fer1 = new MeanVar();
            arc1 = new FiniteQueueStream(16);
            arc2 = new FiniteQueueStream(16);
            arq1 = new StopAndWait(arc1, arc2);
            bch1 = new ByteChannel(rnd0, bc1err, arc2, arc1);
        }

        /// <summary>
        /// Builds a 2-protocol object graph, one connected to the other
        /// </summary>
        /// <param name="bc1err">byte error probability for channel 1</param>
        /// <param name="bc2err">byte error probability for channel 2</param>
        void BuildGraph2P(double bce1, double bce2)
        {
            this.bce1 = bce1;
            this.bce2 = bce2;

            ber1 = new MeanVar();
            fer1 = new MeanVar();
            ber2 = new MeanVar();
            fer2 = new MeanVar();

            arc1 = new FiniteQueueStream(16);
            arc2 = new FiniteQueueStream(16);
            arc3 = new FiniteQueueStream(16);
            arc4 = new FiniteQueueStream(16);

            arq1 = new StopAndWait(arc1, arc2);
            arq2 = new StopAndWait(arc3, arc4);
            bch1 = new ByteChannel(rnd0, bce1, arc2, arc1);
            bch2 = new ByteChannel(rnd0, bce2, arc4, arc3);
        }

        void BuildGraphUC(string com, double bce1, double bce2)
        {
            this.bce1 = bce1;
            this.bce2 = bce2;

            ber1 = new MeanVar();
            fer1 = new MeanVar();

            arc1 = new FiniteQueueStream(16);
            arc2 = new FiniteQueueStream(16);
            arq1 = new StopAndWait(arc1, arc2);


            com1 = new SerialPort(com, 9600);
            com1.Open();

            bch1 = new ByteChannel(rnd0, bce1, com1.BaseStream, arc1);
            bch2 = new ByteChannel(rnd0, bce2, arc2, com1.BaseStream);
        }

        /// <summary>
        /// Compare packets and update statistical estimators
        /// </summary>
        /// <param name="p1">packet 1</param>
        /// <param name="p2">packet 2</param>
        /// <param name="ber">BER (Byte Error Rate) estimator</param>
        /// <param name="fer">FER (Frame Error Rate) estimator</param>
        void ComparePkts(byte[] p1, byte[] p2, int len1, int len2, MeanVar myber, MeanVar myfer)
        {
            bool equal = true;
            int  len   = Math.Min(len1, len2);

            Debug.Assert(len1 > 0);
            Debug.Assert(len2 > 0);

            if (len1 != len2)
                equal = false;

            // comprobación de que el paquete rx es igual al tx
            for (int i = 0; i < len; i++)
            {
                if (p1[i] != p2[i])
                {
                    myber.Compute(1);
                    equal = false;
                }
                else
                    myber.Compute(0);
            }

            

            myfer.Compute(equal ? 0 : 1);
        }

        /// <summary>
        /// Print on screen summary with info retrieves from arq protocol object and BER/FER 
        /// statistical estimators.
        /// </summary>
        void PrintSummary(StopAndWait arq, MeanVar ber, MeanVar fer)
        {
            int bercount = (int)Math.Round(ber.Count * ber.Mean);
            int fercount = (int)Math.Round(fer.Count * fer.Mean);

            Console.WriteLine("  PROTOCOL " + arq.ToString());
            Console.WriteLine("");
            Console.WriteLine("  {0,12} tx packets", arq.TxStats.PacketCount);
            Console.WriteLine("  {0,12} rx packets", arq.RxStats.PacketCount);
            Console.WriteLine("  {0,12} rx bytes   corrupted", bercount);
            Console.WriteLine("  {0,12} rx packets corrupted", fercount);
            Console.WriteLine("  {0,12:E2} byte   error rate (arq protocol)", ber.Mean);
            Console.WriteLine("  {0,12:E2} packet error rate (arq protocol)", fer.Mean);
            Console.WriteLine("  {0,12} pkts droped due to rx buffer overflow", arq.RecvPacketDroped);
            Console.WriteLine("");            
            Console.WriteLine("  {0,12:f2}   E[retransmissions]", arq.AvrReTxPerPacket);
            Console.WriteLine("  {0,12:f2} Var[retransmissions]", arq.VarReTxPerPacket);
            Console.WriteLine("  {0,12} Max[retransmissions]", arq.MaxReTxPerPacket);
            Console.WriteLine("  {0,12:f2}   E[packet size] in bytes", arq.TxStats.AvrPacketSize/8);
            Console.WriteLine("  {0,12:f2} Var[packet size] in bytes^2", arq.TxStats.VarPacketSize/8);            
            Console.WriteLine("");
            Console.WriteLine("  {0,12:f2} bps tx rate", arq.TxStats.AverageDataRate);
            Console.WriteLine("  {0,12:f2} bps rx rate", arq.RxStats.AverageDataRate);

        }

        /// <summary>
        /// Test with 1 protocols and 1 producer/consumer thread 
        /// </summary>
        public void Test1P1T(double bce, int pktcount)
        {
            int rxlen;
            int txlen    = StopAndWait.MTU;
            byte[] txbuf = new byte[txlen];
            byte[] rxbuf = new byte[txlen];


            BuildGraph1P(bce);

            Console.WriteLine("Tx {0} pkts of {1} bytes", pktcount, txlen);
            for (int i = 0; i < pktcount; i++)
            {
                // pkt con datos aleatorios
                rnd0.NextBytes(txbuf);
                txlen = rnd0.Next(1, StopAndWait.MTU + 1);  // [1, arq.MTU+1)

                arq1.SendPacket(txbuf, 0, /**/txlen);
                arq1.RecvPacket(rxbuf, 0, out rxlen);

                // si se producen errores de transmisión esta afirmación podría ser falsa
                // la solución es comentarla simplemente y llamar a ComparePkts() con una
                // longitud de paquete igual a Min(txlen, rxlen)
                Debug.Assert(txlen == rxlen);

                ComparePkts(txbuf, rxbuf, txlen, rxlen, ber1, fer1);
                Console.Write("\r  Pkt={0,4} E[Rt]={1,5:f2} TxRate={2,8:f2}bps",
                    i, arq1.AvrReTxPerPacket, arq1.TxStats.AverageDataRate);
            }

            PrintSummary(arq1, ber1, fer1);
        }

        /// <summary>
        /// Test with 2 protocols and N producer threads and N consumer threads
        /// </summary>
        /// <param name="bce1"></param>
        /// <param name="bce2"></param>
        /// <param name="pcpairs"></param>
        /// <param name="pktcount"></param>
        public void Test2PNT(double bce1, double bce2, int pcpairs, int pktcount)
        {
            TaskArgs a = new TaskArgs();
            BuildGraph2P(bce1, bce2);

            // Launch Producers and Consumers over Protocol 1
            a.protocol = arq1;
            a.pktcount = pktcount;
            a.ber      = ber1;
            a.fer      = fer1;

            LaunchProducers(pcpairs, a);
            LaunchConsumers(pcpairs, a);

            // Launch Producers and Consumers over Protocol 2
            a.protocol = arq2;
            a.pktcount = pktcount;
            a.ber      = ber2;
            a.fer      = fer2;

            LaunchProducers(pcpairs, a);
            LaunchConsumers(pcpairs, a);

            // Wait for finalization of all threads: producers and consumers in both protocols
            do
            {
                Thread.Sleep(100);
                Console.CursorLeft = 0;
                Console.CursorTop -= 2;

                Console.WriteLine("P1 TxPkt={0,4} RxPkt={1,4} E[Rt]={2,4:f1} TxR={3,6:f1}bps RxR={4,6:f1}bps",
                    arq1.TxStats.PacketCount, arq1.TxStats.PacketCount, arq1.AvrReTxPerPacket,
                    arq1.TxStats.AverageDataRate, arq1.RxStats.AverageDataRate);
                Console.WriteLine("P2 TxPkt={0,4} RxPkt={1,4} E[Rt]={2,4:f1} TxR={3,6:f1}bps RxR={4,6:f1}bps",
                    arq2.TxStats.PacketCount, arq2.TxStats.PacketCount, arq2.AvrReTxPerPacket,
                    arq2.TxStats.AverageDataRate, arq2.RxStats.AverageDataRate);
            }
            while (
                (pktsSent < 2 * pcpairs * pktcount) ||
                (pktsRecv < 2 * pcpairs * pktcount));

            Console.WriteLine("  ----------------------------------------------------------------");
            PrintSummary(arq1, ber1, fer1);
            Console.WriteLine("  ----------------------------------------------------------------");
            PrintSummary(arq2, ber2, fer2);
            Console.WriteLine("  ----------------------------------------------------------------");
        }

        /// <summary>
        /// Launches producer threads.
        /// </summary>
        void LaunchProducers(int producers, TaskArgs arg)
        {
            Thread producer;
            for (int i = 0; i < producers; i++)
            {
                arg.id = i;
                producer = new Thread(Producer);
                producer.Start(arg);
            }
        }

        /// <summary>
        /// Launches consumer threads.
        /// </summary>
        void LaunchConsumers(int consumers, TaskArgs arg)
        {
            Thread consumer;
            for (int i = 0; i < consumers; i++)
            {
                arg.id = i;
                consumer = new Thread(Consumer);
                consumer.Start(arg);
            }
        }

        /// <summary>
        /// Producer Thread
        /// </summary>
        /// <param name="arg"></param>    
        void Producer(object arg)
        {
            TaskArgs p = (TaskArgs)arg;

            for (int i = 0; i < p.pktcount; i++)
            {
                while (p.protocol.SendPacket(testpkt, 0, StopAndWait.MTU) == 0)
                    p.protocol.WakeUpSuspendedThreads();

                Interlocked.Increment(ref pktsSent);
            }
        }

        /// <summary>
        /// Consumer Thread
        /// </summary>
        /// <param name="arg"></param>
        void Consumer(object arg)
        {
            TaskArgs p = (TaskArgs)arg;
            int len;
            byte[] recvpkt = new byte[testpkt.Length];

            for (int i = 0; i < p.pktcount; i++)
            {
                // insist if link error
                while (p.protocol.RecvPacket(recvpkt, 0, out len) == 0)
                    p.protocol.WakeUpSuspendedThreads();

                lock (sync)
                {
                    // in case many consumer threads are launched
                    ComparePkts(testpkt, recvpkt, len, len, p.ber, p.fer);
                }

                Interlocked.Increment(ref pktsRecv);
            }
        }

        /// <summary>
        /// Test with UC
        /// </summary>
        /// <param name="bce"></param>
        /// <param name="pktcount"></param>
        public void TestUC(string com, double bce, int pktcount)
        {
            int rxlen;
            int txlen;
            byte[] txbuf = new byte[StopAndWait.MTU];
            byte[] rxbuf = new byte[StopAndWait.MTU];

            BuildGraphUC(com, bce, bce);

            Console.WriteLine("Echo test. Tx/Rx {0} packets...", pktcount);

            for (int i = 0; i < pktcount; i++)
            {
                // pkt con datos aleatorios
                rnd0.NextBytes(txbuf);
                txlen = rnd0.Next(1, StopAndWait.MTU + 1);  // [1, arq.MTU+1)

                while (arq1.SendPacket(txbuf, 0, /**/txlen) == 0)
                {
                    Console.WriteLine("Send: Link error. Retrying...");
                    arq1.WakeUpSuspendedThreads();
                }

                while (arq1.RecvPacket(rxbuf, 0, out rxlen) == 0)
                {
                    Console.WriteLine("Recv: Link error. Retrying...");
                    arq1.WakeUpSuspendedThreads();
                }

                ComparePkts(txbuf, rxbuf, txlen, rxlen, ber1, fer1);
                Console.Write("\r  Pkt={0,4} E[Rt]={1,5:f2} TxRate={2,8:f2}bps",
                    i, arq1.AvrReTxPerPacket, arq1.TxStats.AverageDataRate);
            }

            Console.WriteLine("");
            PrintSummary(arq1, ber1, fer1);
        }



        /// <summary>
        /// Receives frames from uC
        /// </summary>
        /// <param name="bce"></param>
        /// <param name="pktcount"></param>
        public void TestUCRX(string com, double bce, int pktcount)
        {
            int rxlen;
            byte[] rxbuf = new byte[LinkProtocol.MTU];
            StreamWriter sw = File.CreateText("rxpkts.txt");

            BuildGraphUC(com, bce, bce);

            Console.WriteLine("Rx {0} pkts...", pktcount);

            for (int i = 0; i < pktcount; i++)
            {
                // pkt con datos aleatorios
                arq1.RecvPacket(rxbuf, 0, out rxlen);
                sw.Write("[ ");
                for (int j = 0; j < rxlen; j++)
                    sw.Write("0x{0:X2} ", rxbuf[j]);
                
                sw.WriteLine("]");                            
                Console.Write("\r  Pkt={0,4} E[Rt]={1,5:f2} TxRate={2,8:f2}bps",
                    i, arq1.AvrReTxPerPacket, arq1.TxStats.AverageDataRate);
            }

            sw.Close();
            Console.WriteLine("");
            PrintSummary(arq1, ber1, fer1);
        }
    } 
}
